package net.ossrs.rtmp;

import java.util.Arrays;

public final class SrsAllocator {

  public class Allocation {

    private byte[] data;
    private int size;

    public Allocation(int size) {
      this.data = new byte[size];
      this.size = 0;
    }

    public byte[] array() {
      return data;
    }

    public int size() {
      return size;
    }

    public void appendOffset(int offset) {
      size += offset;
    }

    public void clear() {
      size = 0;
    }

    public void put(byte b) {
      data[size++] = b;
    }

    public void put(byte b, int pos) {
      data[pos++] = b;
      size = pos > size ? pos : size;
    }

    public void put(short s) {
      put((byte) s);
      put((byte) (s >>> 8));
    }

    public void put(int i) {
      put((byte) i);
      put((byte) (i >>> 8));
      put((byte) (i >>> 16));
      put((byte) (i >>> 24));
    }

    public void put(byte[] bs) {
      System.arraycopy(bs, 0, data, size, bs.length);
      size += bs.length;
    }
  }

  private final int individualAllocationSize;
  private volatile int availableSentinel;
  private Allocation[] availableAllocations;

  /**
   * Constructs an instance without creating any {@link Allocation}s up front.
   *
   * @param individualAllocationSize The length of each individual {@link Allocation}.
   */
  public SrsAllocator(int individualAllocationSize) {
    this(individualAllocationSize, 0);
  }

  /**
   * Constructs an instance with some {@link Allocation}s created up front.
   * <p>
   *
   * @param individualAllocationSize The length of each individual {@link Allocation}.
   * @param initialAllocationCount The number of allocations to create up front.
   */
  public SrsAllocator(int individualAllocationSize, int initialAllocationCount) {
    this.individualAllocationSize = individualAllocationSize;
    this.availableSentinel = initialAllocationCount + 10;
    this.availableAllocations = new Allocation[availableSentinel];
    for (int i = 0; i < availableSentinel; i++) {
      availableAllocations[i] = new Allocation(individualAllocationSize);
    }
  }

  public synchronized Allocation allocate(int size) {
    for (int i = 0; i < availableSentinel; i++) {
      if (availableAllocations[i].size() >= size) {
        Allocation ret = availableAllocations[i];
        availableAllocations[i] = null;
        return ret;
      }
    }

    return new Allocation(size > individualAllocationSize ? size : individualAllocationSize);
  }

  public synchronized void release(Allocation allocation) {
    allocation.clear();

    for (int i = 0; i < availableSentinel; i++) {
      if (availableAllocations[i].size() == 0) {
        availableAllocations[i] = allocation;
        return;
      }
    }

    if (availableSentinel + 1 > availableAllocations.length) {
      availableAllocations = Arrays.copyOf(availableAllocations, availableAllocations.length * 2);
    }
    availableAllocations[availableSentinel++] = allocation;
  }
}